/*******************************************************************************
 * Copyright (c) 2003, 2007 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.reliablesource.pleiades;

import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.eclipse.core.commands.IHandler;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.commands.ActionHandler;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.CloseWindowListener;
import org.eclipse.swt.browser.LocationAdapter;
import org.eclipse.swt.browser.LocationEvent;
import org.eclipse.swt.browser.OpenWindowListener;
import org.eclipse.swt.browser.ProgressAdapter;
import org.eclipse.swt.browser.ProgressEvent;
import org.eclipse.swt.browser.StatusTextEvent;
import org.eclipse.swt.browser.StatusTextListener;
import org.eclipse.swt.browser.TitleEvent;
import org.eclipse.swt.browser.TitleListener;
import org.eclipse.swt.browser.WindowEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IViewReference;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.WorkbenchException;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.part.ViewPart;
import org.mozilla.interfaces.nsIDOMElement;
import org.mozilla.interfaces.nsIDOMNamedNodeMap;
import org.mozilla.interfaces.nsIDOMNode;
import org.mozilla.interfaces.nsIDOMNodeList;
import org.mozilla.interfaces.nsIDOMRange;
import org.mozilla.interfaces.nsIDOMWindow;
import org.mozilla.interfaces.nsISelection;
import org.mozilla.interfaces.nsIWebBrowser;
import org.reliablesource.model.core.IReliableElement;

/**
 * The Browser view. This consists of a <code>Browser</code> control, and an
 * address bar consisting of a <code>Label</code> and a <code>Text</code>
 * control. This registers handling actions for the retargetable actions added
 * by <code>BrowserActionBuilder</code> (Back, Forward, Stop, Refresh). This
 * also hooks listeners on the Browser control for status and progress messages,
 * and redirects these to the status line.
 * 
 * @since 3.0
 */
public class BrowserView extends ViewPart implements ISelectionProvider {

	/**
	 * Debug flag. When true, status and progress messages are sent to the
	 * console in addition to the status line.
	 */
	private static final boolean DEBUG = true;

	private Browser browser;
	private Text url;
	private String initialUrl;
	private ISelection currentSelection;
	private final List<ISelectionChangedListener> selectionListeners = new LinkedList<ISelectionChangedListener>();
	final private StringBuffer selectedString = new StringBuffer();

	private final Action backAction = new Action("Back") {
		@Override
		public void run() {
			browser.back();
		}
	};

	private final Action forwardAction = new Action("Forward") {
		@Override
		public void run() {
			browser.forward();
		}
	};

	private final Action stopAction = new Action("Stop") {
		@Override
		public void run() {
			browser.stop();
			// cancel any partial progress.
			getViewSite().getActionBars().getStatusLineManager()
					.getProgressMonitor().done();
		}
	};

	private final Action refreshAction = new Action("Refresh") {
		@Override
		public void run() {
			browser.refresh();
		}
	};

	/**
	 * The easter egg action. See the corresponding command and key binding in
	 * the plugin.xml, and how it's registered in createBrowser.
	 */
	private final Action easterEggAction = new Action() {
		{
			setActionDefinitionId(IBrowserConstants.COMMAND_PREFIX
					+ "easterEgg");} //$NON-NLS-1$

		@Override
		public void run() {
			browser.execute("window.confirm('You found the easter egg!')");
		}
	};

	/**
	 * Finds the first browser view in the given window.
	 * 
	 * @param _window
	 *            the window
	 * @return the first found browser view, or <code>null</code> if none
	 *         found
	 */
	private static BrowserView findBrowser(final IWorkbenchWindow _window) {
		IWorkbenchPage page = _window.getActivePage();
		IViewPart view = page.findView(IBrowserConstants.BROWSER_VIEW_ID);
		if (view != null) {
			return (BrowserView) view;
		}
		IViewReference[] refs = page.getViewReferences();
		for (int i = 0; i < refs.length; i++) {
			if (IBrowserConstants.BROWSER_VIEW_ID.equals(refs[i].getId())) {
				return (BrowserView) refs[i].getPart(true);
			}
		}
		return null;
	}

	/**
	 * Constructs a new <code>BrowserView</code>.
	 */
	public BrowserView() {
		initialUrl = BrowserPlugin.getDefault().getPluginPreferences()
				.getString(IBrowserConstants.PREF_HOME_PAGE);
	}

	@Override
	public void init(final IViewSite _site, final IMemento _memento)
			throws PartInitException {
		super.init(_site);
		if (_memento != null) {
			String u = _memento.getString(IBrowserConstants.MEMENTO_URL);
			if (u != null) {
				initialUrl = u;
			}
		}
	}

	@Override
	public void saveState(final IMemento _memento) {
		_memento.putString(IBrowserConstants.MEMENTO_URL, browser.getUrl());
	}

	@Override
	public void createPartControl(final Composite _parent) {
		browser = createBrowser(_parent, getViewSite().getActionBars());
		browser.setUrl(initialUrl);
		getSite().setSelectionProvider(this);
	}

	@Override
	public void setFocus() {
		if (browser != null && !browser.isDisposed()) {
			browser.setFocus();
		}
	}

	private Browser createBrowser(Composite parent, final IActionBars actionBars) {

		GridLayout gridLayout = new GridLayout();
		gridLayout.numColumns = 2;
		parent.setLayout(gridLayout);

		Label labelAddress = new Label(parent, SWT.NONE);
		labelAddress.setText("A&ddress");

		url = new Text(parent, SWT.BORDER);
		GridData data = new GridData();
		data = new GridData();
		data.horizontalAlignment = GridData.FILL;
		data.grabExcessHorizontalSpace = true;
		url.setLayoutData(data);

		browser = new Browser(parent, SWT.MOZILLA);
		data = new GridData();
		data.horizontalAlignment = GridData.FILL;
		data.verticalAlignment = GridData.FILL;
		data.horizontalSpan = 2;
		data.grabExcessHorizontalSpace = true;
		data.grabExcessVerticalSpace = true;
		browser.setLayoutData(data);

		browser.addProgressListener(new ProgressAdapter() {
			IProgressMonitor monitor = actionBars.getStatusLineManager()
					.getProgressMonitor();
			boolean working = false;
			int workedSoFar;

			@Override
			public void changed(ProgressEvent event) {
				if (DEBUG) {
					System.out.println("changed: " + event.current + "/"
							+ event.total);
				}
				if (event.total == 0)
					return;
				if (!working) {
					if (event.current == event.total)
						return;
					monitor.beginTask("", event.total); //$NON-NLS-1$
					workedSoFar = 0;
					working = true;
				}
				monitor.worked(event.current - workedSoFar);
				workedSoFar = event.current;
			}

			@Override
			public void completed(ProgressEvent event) {
				if (DEBUG) {
					System.out.println("completed: " + event.current + "/"
							+ event.total);
				}
				monitor.done();
				working = false;
			}
		});
		browser.addStatusTextListener(new StatusTextListener() {
			IStatusLineManager status = actionBars.getStatusLineManager();

			public void changed(StatusTextEvent event) {
				if (DEBUG) {
					System.out.println("status: " + event.text);
				}
				status.setMessage(event.text);
			}
		});
		browser.addLocationListener(new LocationAdapter() {
			@Override
			public void changed(LocationEvent event) {
				if (event.top)
					url.setText(event.location);
			}
		});
		browser.addTitleListener(new TitleListener() {
			public void changed(TitleEvent event) {
				setPartName(event.title);
			}
		});
		browser.addOpenWindowListener(new OpenWindowListener() {
			public void open(WindowEvent event) {
				BrowserView.this.openWindow(event);
			}
		});
		// TODO: should handle VisibilityWindowListener.show and .hide events
		browser.addCloseWindowListener(new CloseWindowListener() {
			public void close(WindowEvent event) {
				BrowserView.this.close();
			}
		});

		browser.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseDoubleClick(final MouseEvent _e) {
				getSelectedText();
			}

			@Override
			public void mouseUp(final MouseEvent _e) {
				getSelectedText();
			}

			private void getSelectedText() {
				nsIWebBrowser webBrowser = (nsIWebBrowser) browser
						.getWebBrowser();
				nsIDOMWindow window = webBrowser.getContentDOMWindow();

				nsISelection _selection = window.getSelection();

				// don't do anything if no 'range' objects.
				if (_selection.getRangeCount() < 1)
					return;

				nsIDOMRange _range = _selection.getRangeAt(0);

				if (_range.getCollapsed())
					return;

				String _startNodeName = _range.getStartContainer()
						.getParentNode().getNodeName();
				int _startOffset = _range.getStartOffset();

				String _endNodeName = _range.getEndContainer().getParentNode()
						.getNodeName();
				int _endOffset = _range.getEndOffset();

				String _selText = null;
				if (_range.getStartContainer().equals(_range.getEndContainer())) {
					_selText = _range.getEndContainer().getNodeValue()
							.subSequence(_startOffset, _endOffset).toString();
				} else {
					_selText = _range.getStartContainer().getNodeValue()
							.substring(_startOffset);
					_selText += _range.getEndContainer().getNodeValue()
							.subSequence(0, _endOffset);
				}

				if (_selText.trim().equals(selectedString.toString().trim()))
					return;

				int _length = selectedString.toString().length();
				if (_length > 0)
					selectedString.delete(0, _length);

				selectedString.append(_selText.trim());

				nsIDOMElement _element = window.getDocument().createElement(
						"font");

				_element.setAttribute("style",
						"color: black; background-color: yellow;");

				System.err.println("start---XPath-----");

				String _XPath = parseXPath(_range.getStartContainer());

				System.err.println("xpath:\t" + _XPath);
				System.err.println("end----XPath-----");

				_range.surroundContents(_element);

				if (selectedString.toString().length() > 0) {

					ReliableTextRange _rRange = new ReliableTextRange(
							selectedString.toString(), _startOffset, _endOffset);

					ReliableSnippet _snippet = new ReliableSnippet(_rRange);
					_snippet.setDescription(_rRange.getContent());
					Date _date = new Date();
					_snippet.setDate(_date.toString());
					_snippet.setParent(BrowserPlugin.getDefault()
							.getResourceLink(url.getText().trim()));
					_snippet.setXPath(_XPath);
					setSelection(new StructuredSelection(_snippet));

					if (DEBUG) {

						nsIDOMNodeList _list = window.getDocument()
								.getChildNodes();
						long _len = _list.getLength();
						for (int i = 0; i < _len; i++)
							parseDOM(_list.item(i), "   ");

						System.err.println();
						System.err
								.println("-----------------------------------------------");
						System.err.println("uri of selected text:  "
								+ _snippet.getParent().getId());
						//$NON-NLS-1$
						System.err
								.println("Start container: " + _startNodeName);
						//$NON-NLS-1$
						System.err.println("Start offset: " + _startOffset);
						//$NON-NLS-1$

						System.err.println("End container: " + _endNodeName);
						//$NON-NLS-1$
						System.err.println("End offset: " + _endOffset);
						//$NON-NLS-1$

						System.err.println("Selected text: " + _selText);
						//$NON-NLS-1$
						System.err.println();
					}

				}

			}

		});

		url.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
				browser.setUrl(BrowserPlugin.getDefault().getResourceLink(
						url.getText().trim()).getId());
			}
		});

		// Hook the navigation actons as handlers for the retargetable actions
		// defined in BrowserActionBuilder.
		actionBars.setGlobalActionHandler("back", backAction); //$NON-NLS-1$
		actionBars.setGlobalActionHandler("forward", forwardAction); //$NON-NLS-1$
		actionBars.setGlobalActionHandler("stop", stopAction); //$NON-NLS-1$
		actionBars.setGlobalActionHandler("refresh", refreshAction); //$NON-NLS-1$

		// Register the easter egg action with the key binding service,
		// allowing it to be invoked directly via keypress,
		// without requiring the action to be visible in the UI.
		// Note that the address field needs to have focus for this to work,
		// or any control other than the browser widget, due to
		// <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=69919">bug
		// 69919</a>.
		IHandlerService hs = (IHandlerService) getSite().getService(
				IHandlerService.class);
		IHandler easterHandler = new ActionHandler(easterEggAction);
		hs.activateHandler(easterEggAction.getActionDefinitionId(),
				easterHandler);
		return browser;
	}

	/**
	 * Opens a new browser window.
	 * 
	 * @param event
	 *            the open window event
	 */
	private void openWindow(WindowEvent event) {
		try {
			IWorkbench workbench = getSite().getWorkbenchWindow()
					.getWorkbench();
			IWorkbenchWindow window = workbench.openWorkbenchWindow(
					IBrowserConstants.BROWSER_PERSPECTIVE_ID, null);
			Shell shell = window.getShell();
			if (event.location != null)
				shell.setLocation(event.location);
			if (event.size != null)
				shell.setLocation(event.size);
			BrowserView view = findBrowser(window);
			Assert.isNotNull(view);
			event.browser = view.browser;
		} catch (WorkbenchException e) {
			BrowserPlugin.getDefault().log(e);
		}
	}

	/**
	 * Closes this browser view. Closes the window too if there are no
	 * non-secondary parts open.
	 */
	private void close() {
		IWorkbenchPage page = getSite().getPage();
		IWorkbenchWindow window = page.getWorkbenchWindow();
		page.hideView(this);
		if (BrowserPlugin.getNonSecondaryParts(page).size() == 0) {
			page.closePerspective(page.getPerspective(), true, true);
		}
		if (window.getActivePage() == null) {
			window.close();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.jface.viewers.ISelectionProvider#addSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener)
	 */
	public void addSelectionChangedListener(
			final ISelectionChangedListener _listener) {
		if (_listener != null)
			if (!selectionListeners.contains(_listener))
				selectionListeners.add(_listener);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.jface.viewers.ISelectionProvider#getSelection()
	 */
	public ISelection getSelection() {
		if (currentSelection != null)
			return currentSelection;
		return StructuredSelection.EMPTY;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.jface.viewers.ISelectionProvider#removeSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener)
	 */
	public void removeSelectionChangedListener(
			final ISelectionChangedListener _listener) {
		if (_listener != null)
			if (selectionListeners.contains(_listener))
				selectionListeners.remove(_listener);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.jface.viewers.ISelectionProvider#setSelection(org.eclipse.jface.viewers.ISelection)
	 */
	public void setSelection(ISelection _selection) {
		if (null == _selection)
			_selection = StructuredSelection.EMPTY;

		if (_selection.equals(currentSelection))
			return;

		currentSelection = _selection;

		BrowserPlugin
				.getDefault()
				.getResourceLink(url.getText().trim())
				.addChild(
						(IReliableElement) ((StructuredSelection) currentSelection)
								.getFirstElement());

		notifyListeners();
	}

	// This method should only be called by #setSelection()
	private void notifyListeners() {
		Iterator<ISelectionChangedListener> _it = selectionListeners.iterator();
		SelectionChangedEvent _selChngdEvt = new SelectionChangedEvent(this,
				getSelection());
		while (_it.hasNext())
			_it.next().selectionChanged(_selChngdEvt);
	}

	private void parseDOM(nsIDOMNode _node, String spaces) {

		nsIDOMNodeList _list = _node.getChildNodes();
		String _nodeName = _node.getNodeName();
		// System.err.println("Node Name: " + spaces + _nodeName);
		long _len = _list.getLength();
		for (int i = 0; i < _len; i++) {

			String _prop = _list.item(i).getNodeName();
			String _val = _list.item(i).getNodeValue();
			// System.err.println("PROP: " + spaces + _prop);
			if (_val != null && _val.trim().length() > 0)
				System.err.println(spaces + "Prop: " + _prop + " , Value: "
						+ _val);
			// System.err.println(spaces + _val);
			parseDOM(_list.item(i), spaces + "  ");

		}

	}

	private String parseXPath(final nsIDOMNode _node) {
		nsIDOMNode _parentNode = _node.getParentNode();
		StringBuffer _nodeName = new StringBuffer();
		String _name = _node.getNodeName();
		// String _nodePrefix = _node.getPrefix();
		// _node.getAttributes().getNamedItem(_name).
		_nodeName.append(_name);

		String _innerPath = "";
		if (_node.hasAttributes()) {
			nsIDOMNamedNodeMap attrs = _node.getAttributes();
			long nAttributes = attrs.getLength();

			for (int i = 0; i < nAttributes; i++) {
				nsIDOMNode _node2 = attrs.item(i);
				_innerPath += " : " + parseXPath(_node2);
			}

			_innerPath += " ::: ";
		}
		if (null != _node.getNodeValue()) {
			_nodeName.append(" (" + _node.getNodeValue() + ") ");
		}

		_nodeName.insert(0, "-->");

		String _output = _nodeName.toString().trim() + _innerPath;

		if (_parentNode != null)
			return parseXPath(_parentNode) + _output;

		return _output;
	}

	// public boolean showAnnotation(ReliableSnippet _snippet){
	// nsIWebBrowser webBrowser = (nsIWebBrowser) browser
	// .getWebBrowser();
	// nsIDOMWindow window = webBrowser.getContentDOMWindow();
	//		
	// nsISelection _selection = window.getSelection();
	//		
	// window.getDocument().getElementById(initialUrl);
	// // ((nsIDOMWindow2)browser.getWebBrowser()).getDocument().
	//		
	// // ((nsIDOMWindow2)browser.getWebBrowser()).
	// nsIDOMRange _range = _selection.getRangeAt(0);
	//		
	// if (_range.getCollapsed())
	// return;
	//		
	// String _startNodeName = _range.getStartContainer()
	// .getParentNode().getNodeName();
	// int _startOffset = _range.getStartOffset();
	//		
	// String _endNodeName = _range.getEndContainer().getParentNode()
	// .getNodeName();
	// int _endOffset = _range.getEndOffset();
	//		
	// String _selText = null;
	// if (_range.getStartContainer().equals(_range.getEndContainer())) {
	// _selText = _range.getEndContainer().getNodeValue()
	// .subSequence(_startOffset, _endOffset).toString();
	// } else {
	// _selText = _range.getStartContainer().getNodeValue()
	// .substring(_startOffset);
	// _selText += _range.getEndContainer().getNodeValue()
	// .subSequence(0, _endOffset);
	// }
	//		
	// if (_selText.trim().equals(selectedString.toString().trim()))
	// return;
	//		
	// int _length = selectedString.toString().length();
	// if (_length > 0)
	// selectedString.delete(0, _length);
	//		
	// selectedString.append(_selText.trim());
	//		
	// nsIDOMElement _element = window.getDocument().createElement(
	// "font");
	//		
	// _element.setAttribute("style",
	// "color: black; background-color: yellow;");
	//		
	// System.err.println("start---XPath-----");
	//		
	// String _XPath = parseXPath(_range.getStartContainer());
	//		
	// System.err.println("xpath:\t" + _XPath);
	// System.err.println("end----XPath-----");
	//		
	// _range.surroundContents(_element);
	//		
	// if (selectedString.toString().length() > 0) {
	//		
	// ReliableTextRange _rRange = new ReliableTextRange(
	// selectedString.toString(), _startOffset, _endOffset);
	//		
	// ReliableSnippet _snippet = new ReliableSnippet(_rRange);
	// _snippet.setDescription(_rRange.getContent());
	// Date _date = new Date();
	// _snippet.setDate(_date.toString());
	// _snippet.setParent(BrowserPlugin.getDefault()
	// .getResourceLink(url.getText().trim()));
	// _snippet.setXPath(_XPath);
	// setSelection(new StructuredSelection(_snippet));
	// }
}
